/*
 * The NI class
 */
package ni;

import ctrlr.ChatNI2Ctrlr;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import model.ChatNI2ModelCtrlr;
import signals.SignalTooBigException;

/**
 *
 * @author gb
 */
public class ChatNI implements ChatCtrlr2NI {

    private static final boolean chatNIVerbose = true;
    static final int receivePort = 4444;
    private ChatNI2Ctrlr ctrlr;
    private ChatNI2ModelCtrlr modelCtrlr;
    private UDPSender udpSender;
    private UDPServer udpServer;
    private ArrayList<TCPSender_1> tcpSenderRunning;
    private TCPReceiver_1 tcpReceiverRunning;

    /**
     * Creates a ChatNI object
     * 
     * @param ctrlr the controller to have a link to the GUI
     * @param modelCtrlr the controller to have a link with the Model
     */
    public ChatNI(ChatNI2Ctrlr ctrlr, ChatNI2ModelCtrlr modelCtrlr) {
        this.ctrlr = ctrlr;
        this.modelCtrlr = modelCtrlr;
        this.tcpSenderRunning = new ArrayList<TCPSender_1>();
        //this.tcpServer = new TCPServer(this);
        //this.srvSock = new ServerSocket(receivePort);
    }

    static void printDebug(String callingClass, String callingFunction, String actionPerformed) {
        if (ChatNI.chatNIVerbose) {
            System.out.println("In " + callingClass + " -> " + callingFunction);
            System.out.println("\t" + actionPerformed);
        }
    }

    static void printError(String callingClass, String callingFunction, Exception e) {
        System.out.println("Error in " + callingClass + " -> " + callingFunction);
        System.out.println("\t" + e.getMessage());
    }

    static InetAddress extractIpFromUsername(String username) throws UnknownHostException {
        String[] usernameSplit = username.split("@");
        return InetAddress.getByName(usernameSplit[1]);
    }

    @Override
    public void sendHello() {
        try {
            printDebug("ChatNI", "sendHello", "Starting UDP Server and Sender");
            DatagramSocket sock = new DatagramSocket(receivePort);
            sock.setBroadcast(true);
            this.udpSender = new UDPSender(sock);
            this.udpServer = new UDPServer(this, sock);
            
            udpServer.start();
            //------- NOT USING tcpServer anymore -------//
            //printDebug("ChatNI", "sendHello", "Starting TCP Server");
            //tcpServer.start();

            InetAddress bdip = IpGetter.getLocalBroadcastIp();
            InetAddress lip = IpGetter.getLocalIp();
            printDebug("ChatNI", "sendHello", "Asking controller to set bd ip in the ctrlr");
            this.modelCtrlr.setBroadcastIpAddress(bdip);
            String netname = this.modelCtrlr.getUsername() + "@" + lip.getHostAddress();
            printDebug("ChatNI", "sendHello", "Asking controller to set netName in model to " + netname);
            this.modelCtrlr.setNetUsername(netname);

            printDebug("ChatNI", "sendHello", "Asking udpSender to send a hello message in broadcast");
            udpSender.sendHello(netname, bdip);
        } catch (SocketException ex) {
            printError(ChatNI.class.getName(), "sendHello", ex);
            System.exit(1);
        } catch (SignalTooBigException ex) {
            printError(ChatNI.class.getName(), "sendHello", ex);
            System.exit(1);
        } catch (IOException ex) {
            printError(ChatNI.class.getName(), "sendHello", ex);
            System.exit(1);
        }
    }

    void sendHelloOK(String remoteUsername) {
        try {
            printDebug("ChatNI", "sendHelloOK", "Asking ChatCtrlr to get the netName from the model");
            String netName = this.modelCtrlr.getNetUsername();
            printDebug("ChatNI", "sendHelloOK", "Asking udpSender to send a helloOK");
            udpSender.sendHelloOK(netName, remoteUsername);
        } catch (SignalTooBigException ex) {
            printError(ChatNI.class.getName(), "sendHelloOK", ex);
            System.exit(1);
        } catch (IOException ex) {
            printError(ChatNI.class.getName(), "sendHelloOK", ex);
            System.exit(1);
        }
    }

    @Override
    public void sendGoodbye() {
        try {
            printDebug("ChatNI", "sendGoodbye", "Asking udpSender to send a Goodbye");
            udpSender.sendGoodbye(this.modelCtrlr.getNetUsername(), this.modelCtrlr.getBroadcastIpAddress());
            printDebug("ChatNI", "processGoodbye", "Asking closing all the threads ");
            closeTCPReceiver();
            closeTCPSenders();
            closeUDPServer();
        } catch (SignalTooBigException ex) {
            printError(ChatNI.class.getName(), "sendGoodbye", ex);
            System.exit(1);
        } catch (IOException ex) {
            printError(ChatNI.class.getName(), "sendGoodbye", ex);
            System.exit(1);
        }
    }

    @Override
    public void sendTextMessage(String message, ArrayList<String> to) {
        try {
            printDebug("ChatNI", "sendTextMessage", "Asking udpSender to send a TextMessage");
            udpSender.sendTextMessage(message, this.modelCtrlr.getNetUsername(), to);
            printDebug("ChatNI", "sendTextMessage", "Asking modelCtrlr to add SentTextMessage to the chat msgs");
            this.modelCtrlr.addSentTextMessage(message, to);
        } catch (SignalTooBigException ex) {
            printError(ChatNI.class.getName(), "sendTextMessage", ex);
            System.exit(1);
        } catch (IOException ex) {
            printError(ChatNI.class.getName(), "sendTextMessage", ex);
            System.exit(1);
        }
    }

    /*
     @Override
     public void sendFile(String fileName, File file, ArrayList<String> to) {
     printDebug("ChatNI", "sendFile", "sending a file to " + to.toString());
     for (String remoteUser : to) {
     TCPSender ts = new TCPSender(fileName, file, this.modelCtrlr.getNetUsername(), to, remoteUser);
     ts.start();
     }
     }
     */
    @Override
    public void sendFileProposal(Path filePath, ArrayList<String> to) {
        String fileName = filePath.getFileName().toString();
        long size = -1;
        try {
            size = Files.size(filePath);
        } catch (IOException ex) {
            System.err.println("In ChatNI : Could not get the file Size");
        }
        printDebug("ChatNI", "sendFileProposal", "Getting the net name from the model ctrlr");
        String from = this.modelCtrlr.getNetUsername();
        try {
            printDebug("ChatNI", "sendFileProposal", "Asking UDPSender to send a file proposal " + fileName + " ( " + (int) size + ")" + " to :" + to);
            this.udpSender.sendFileProposal(fileName, size, from, to);
            for (String curRmUser : to) {
                this.modelCtrlr.rememberFileProposal(fileName, filePath, curRmUser);
            }
        } catch (SignalTooBigException ex) {
            System.err.println("ChatNI : Could not send file proposal, Signal is too big.");
        } catch (IOException ex) {
            System.err.println("ChatNI : Could not send file proposal.");
        }
    }

    @Override
    public void sendFileTransferAccepted(String filename, long fileSize, String remoteNetname, ArrayList<String> to) {
        if (tcpReceiverRunning == null ) {
            printDebug("ChatNI", "sendFileTransferAccepted", "Getting the netName");
            String netName = this.modelCtrlr.getNetUsername();
            printDebug("ChatNI", "sendFileTransferAccepted", "Creating the TCP receiver and starting it");
            tcpReceiverRunning = new TCPReceiver_1(this, filename, fileSize, remoteNetname, to);
            tcpReceiverRunning.start();
            printDebug("ChatNI", "sendFileTransferAccepted", "Asking UDPSender to send a file transfer ok for " + filename + " from " + remoteNetname);
            try {
                this.udpSender.sendFileTransferAccepted(filename, netName, remoteNetname);
            } catch (SignalTooBigException ex) {
                System.err.println("ChatNI : Could not send file transfer ok, Signal is too big.");
            } catch (IOException ex) {
                System.err.println("ChatNI : Could not send file transfer ok.");
            }
        }
        else {
            printDebug("ChatNI", "sendFileTransferAccepted", "Could not accept file we are already receiving one");
            ArrayList<String> empty = new ArrayList<String>();
            empty.add("System");
            this.modelCtrlr.addReceivedTextMessage("Could not accept file " + filename + " already receiving one.", "System", empty);
            this.sendFileTransferNotAccepted(filename, fileSize, remoteNetname, to);
        }
    }

    @Override
    public void sendFileTransferNotAccepted(String filename, long fileSize, String remoteNetname, ArrayList<String> to) {
        printDebug("ChatNI", "sendFileTransferNotAccepted", "Getting the netName");
        String netName = this.modelCtrlr.getNetUsername();
        printDebug("ChatNI", "sendFileTransferNotAccepted", "Asking UDPSender to send a file transfer not ok for " + filename + " from " + remoteNetname);
        try {
            this.udpSender.sendFileTransferNotAccepted(filename, netName, remoteNetname);
            processFileReception(filename, remoteNetname, to, false);
        } catch (SignalTooBigException ex) {
            System.err.println("ChatNI : Could not send file transfer ok, Signal is too big.");
        } catch (IOException ex) {
            System.err.println("ChatNI : Could not send file transfer ok.");
        }
    }

    void processHello(String remoteUsername) {
        String netUsername;
        netUsername = this.modelCtrlr.getNetUsername();
        if (netUsername.equals(remoteUsername)) {
            printDebug("ChatNI", "processHello", "Not doing anything it is us");

        } else {
            printDebug("ChatNI", "processHello", "Asking model ctrlr to add " + remoteUsername + " to the connected user list");
            this.modelCtrlr.addUserToList(remoteUsername);
            sendHelloOK(remoteUsername);
        }
    }

    void processHelloOK(String remoteUsername) {
        printDebug("ChatNI", "processHelloOK", "Asking model ctrlr to add " + remoteUsername + " to the connected user list");
        this.modelCtrlr.addUserToList(remoteUsername);
    }

    void processGoodbye(String remoteUsername) {
        printDebug("ChatNI", "processGoodbye", "Asking model ctrlr to remove " + remoteUsername + " from the connected user list");
        this.modelCtrlr.delUserFromList(remoteUsername);
    }

    void processTextMessage(String message, String from, ArrayList<String> to) {
        printDebug("ChatNI", "processTextMessage", "Asking model ctrlr to add the message " + message + " from " + from + " to the chat msgs");
        this.modelCtrlr.addReceivedTextMessage(message, from, to);
    }

    boolean processFileProposal(String fileName, long size, String from, ArrayList<String> to) {
        printDebug("ChatNI", "processFileProposal", "Forwarding to controller" + " \n\t\received a file proposal:" + from + "->" + to.toString() + ":" + fileName + "(" + size + ")");
        return ctrlr.processFileProposal(fileName, size, from, to);
    }

    void processFileReception(String fileName, String from, ArrayList<String> to, boolean ftAccepted) {
        printDebug("ChatNI", "processFileReception", "Adding a line to the ChatMsgs to advise file " + fileName + " reception.");
        this.modelCtrlr.addTextNotifyFileReceived(fileName, from, to, ftAccepted);
    }

    void processfileTransmission(String fileName, String remoteUsername, Boolean transmitted) {
        printDebug("ChatNI", "processFileTransmission", "Adding a line to the ChatMsgs to advise file " + fileName + " transmission.");
        this.modelCtrlr.addTextNotifyFileTransmitted(fileName, remoteUsername, transmitted);
    }

    void processFileTransferAccepted(String fileName, String remoteNetname) {
        //Getting the path for the file we said we would send
        printDebug(this.getClass().getCanonicalName(), "processFileTransferAccepted", "Retrieving file path for " + fileName + " we sent to " + remoteNetname);
        Path filepath = this.modelCtrlr.getFilePathForProposal(fileName, remoteNetname);

        //Sending the file
        printDebug(this.getClass().getCanonicalName(), "processFileTransferAccepted", "Starting file sender.");
        TCPSender_1 tcpsender = new TCPSender_1(this, fileName, filepath.toFile(), remoteNetname);
        tcpsender.start();
        tcpSenderRunning.add(tcpsender);

        //Erasing the sentFileProposal from the map
        printDebug(this.getClass().getCanonicalName(), "processFileTransferAccepted", "Erasing the sentFileProposal from the map.");
        this.modelCtrlr.forgetFileProposal(fileName, remoteNetname);
    }

    void processFileTransferNotAccepted(String fileName, String remoteNetname) {
        //Erasing the sentFileProposal from the map
        printDebug(this.getClass().getCanonicalName(), "processFileTransferNotAccepted", "Erasing the sentFileProposal from the map.");
        this.modelCtrlr.forgetFileProposal(fileName, remoteNetname);
    }

    void processSenderThreadClosure(TCPSender_1 tcpsender) {
        this.tcpSenderRunning.remove(tcpsender);
    }

    void processReceiverThreadClosure(TCPReceiver_1 tcpreceiver) {
        tcpReceiverRunning = null;
    }

    private void closeTCPSenders() {
        printDebug(this.getClass().getCanonicalName(), "closeTCPSenders", "Killing badly all senders.");
        for (TCPSender_1 ts : this.tcpSenderRunning) {
            ts.closeSockets();
            ts.interrupt();
        }
    }

    private void closeTCPReceiver() {
        if (tcpReceiverRunning != null) {
            printDebug(this.getClass().getCanonicalName(), "closeTCPReceiver", "Killing badly the receiver.");
            tcpReceiverRunning.closeSockets();
            tcpReceiverRunning.interrupt();
        }
    }

    private void closeUDPServer() {
        printDebug(this.getClass().getCanonicalName(), "closeUDPServer", "Killing badly the udp server.");
        this.udpServer.closeSockets();
        this.udpServer.interrupt();
    }
}
